import Cesium, { Appearance, GroundPrimitive, Primitive, Rectangle } from '../Ces/Cesium'
import WeSurfaceShader from './WeSurfaceShaders'
import WeUniform from './WeUniform'
import WeDisplayParameter from './WeDisplayParameter'
import WeDisplayShaders from './WeDisplayShaders'
import WeDisplay from './WeDisplay'
import WeDisplayEffectNumber from './WeDisplayEffectNumber'

const { WeDefineHasICON, fixMaterialsSourface } = WeDisplayShaders

interface WeSurfaceOptions {
  dataProvider?: any
  display: any
  rectangle?: Rectangle
  clampToGround?: boolean
  useIconEffect?: boolean
  useGridEffect?: boolean
  effect?: any
  id?: string
}

class WeSurface {
  private _display: WeDisplay
  private _weUniform: WeUniform
  private _extrudedHeight: number
  private _rectangle: Rectangle
  private _clampToGround: boolean
  private _scaleZ: number
  private _translucent: boolean
  private _dirty: boolean
  private show: boolean
  private _primitive: Primitive | GroundPrimitive | undefined
  private _appearance: Appearance | undefined
  private useIconEffect: boolean
  private useGridEffect: boolean
  private _id
  private effect: WeDisplayEffectNumber
  constructor(options: WeSurfaceOptions) {
    this._id = options.id?? Cesium.createGuid()
    this._display = options.display
    this.effect = options.effect

    const rectangle = options.rectangle || this._display.imageryLayer.rectangle
    const floatUniform = {
      sectPositionZ: 0.0,
      surfaceAlpha: 1.0,
    }

    const vec4Uniform = {
      localExtent: rectangle,
    }

    this._weUniform = new WeUniform({ floatUniform, vec4Uniform, name: 'u_WeSurfaceUniform' })

    const size = options.dataProvider?.externalData?.size
    this._extrudedHeight = size ? size.depth : 0.0
    this._rectangle = rectangle
    this._clampToGround = options.clampToGround || false
    this._scaleZ = WeDisplayParameter.scaleZ
    this._translucent = WeDisplayParameter.translucent
    this.useIconEffect = options.useIconEffect?? false
    this.useGridEffect = options.useGridEffect?? false
    this._dirty = true
    this.show = true
  }

  get id() {
    return this._id
  }

  get alpha(): number {
    return this._weUniform.Properties.surfaceAlpha
  }

  set alpha(value: number) {
    this._weUniform.Properties.surfaceAlpha = value
  }

  get extrudedHeight(): number {
    return this._extrudedHeight
  }

  set extrudedHeight(value: number) {
    this._extrudedHeight = value
  }

  get sectPositionZ(): number {
    return this._weUniform.Properties.sectPositionZ
  }

  set sectPositionZ(value: number) {
    if (value >= 0.0 && value <= 1.0 && value !== this._weUniform.Properties.sectPositionZ) {
      this._weUniform.Properties.sectPositionZ = value
      this._dirty = true
    }
  }

  get scaleZ(): number {
    return this._scaleZ
  }

  set scaleZ(value: number) {
    if (this._scaleZ !== value) {
      this._scaleZ = value
      this._dirty = true
    }
  }

  get translucent(): boolean {
    return this._translucent
  }

  set translucent(value: boolean) {
    if (this._translucent !== value) {
      this._translucent = value
      this._dirty = true
    }
  }

  rebuild(context): void {
    this.initAppearance(context)

    const appearance = this._appearance
    const rectangle = this._rectangle.clone()

    if (rectangle.east > Cesium.Math.PI) rectangle.east = Cesium.Math.PI

    const height = WeDisplayParameter.scaleZ * this.extrudedHeight * this.sectPositionZ
    const geometryInstances = [
      new Cesium.GeometryInstance({
        geometry: new Cesium.RectangleGeometry({
          rectangle,
          height,
          granularity: Cesium.Math.RADIANS_PER_DEGREE,
        }),
        id: this,
      }),
    ]
    const asynchronous = true
    const primitive =
      height === 0.0 && this._clampToGround
        ? new Cesium.GroundPrimitive({
          geometryInstances,
          asynchronous,
          appearance,
        })
        : new Cesium.Primitive({
          geometryInstances,
          asynchronous,
          appearance,
        })

    //@ts-ignore
    this._primitive = this._primitive && this._primitive.destroy()

    this._primitive = primitive
  }

  initAppearance(context): void {
    if (this._appearance) {
      return
    }

    const display = this._display
    const uniforms: Record<string, any> = {}
    this._weUniform.setupUniform(uniforms)
    display.setupUniform(uniforms)

    const uniformDeclar = this._weUniform.getSource(false)
    let baseFragmentSource = display.getFragmentShaderSource(true, true, false)

    let iconEffectSource = ''
    if (this.useIconEffect) {
      display.iconEffect._contextTexture = context.weContext.windSymbolTexture
      display.iconEffect._valid = true
      display.iconEffect.setupUniform(uniforms)
      baseFragmentSource = `#define ${WeDefineHasICON}\n${baseFragmentSource}`
      iconEffectSource = display.iconEffect.shaderSource
    } else if (this.useGridEffect && this.effect) {
      baseFragmentSource = `#define ${WeDefineHasICON}\n${baseFragmentSource}`
      const effect = this.effect
      iconEffectSource = effect.getSource()
      effect.setupUniform(uniforms)
    }

    let fragmentShaderSource = `
      ${uniformDeclar}
      ${baseFragmentSource}
      ${display.contourEffect.shaderSource}
      ${iconEffectSource}
      ${WeSurfaceShader}
    `

    fragmentShaderSource = fixMaterialsSourface(fragmentShaderSource, uniforms)

    const translucent = this.translucent
    const appearance = new Cesium.EllipsoidSurfaceAppearance({
      renderState: {
        depthTest: { enabled: true },
        depthMask: true,
        blending: Cesium.BlendingState.ALPHA_BLEND,
        cull: {
          enabled: false,
          face: Cesium.CullFace.BACK,
        },
      },
      translucent,
      material: new Cesium.Material({
        fabric: {
          uniforms,
          source: fragmentShaderSource,
        },
        translucent,
      }),
    })

    display.fixMaterials(appearance.material)

    this._appearance = appearance
  }

  update(frameState): void {
    if (!this.show) return

    if (!frameState.passes.render
      && (this.useIconEffect || this.useGridEffect)) {
      return
    }

    if (this.scaleZ !== WeDisplayParameter.scaleZ) {
      this.scaleZ = WeDisplayParameter.scaleZ
    }
    if (this.translucent !== WeDisplayParameter.translucent) {
      this.translucent = WeDisplayParameter.translucent
    }

    if (this._dirty) {
      this._dirty = false
      this.rebuild(frameState.context)
    }

    //@ts-ignore
    this._primitive?.update(frameState)
  }

  destroy(): any {
    if (this._primitive) {
      this._primitive.destroy()
    }
    return Cesium.destroyObject(this)
  }

  isDestroyed(): boolean {
    return false
  }
}

export default WeSurface
